package org.mockito;

import org.mockito.internal.debugging.MockitoDebuggerImpl;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;

/**
 * An extension which allows for assertions over all Mockito mocks defined in a test class
 * In this way behaviour against ALL test class defined mocks (@Mock) can be asserted.
 *
 * @see org.mockito.MockitoAnnotations
 * @see org.mockito.InOrder
 * @see org.mockito.Mockito
 */
public final class MockitoAnnotationsExtension {

    private static final MockitoDebugger mockitoDebugger = new MockitoDebuggerImpl();

    private static Object[] allAnnotatedMocksCapture = null;

    private MockitoAnnotationsExtension(){}

    public static void initMocksExtension(final Object testClass){
        MockitoAnnotations.initMocks(testClass);
        allAnnotatedMocksCapture = getAllAnnotatedMocks(testClass);
    }

    public static void tearDown(){
        allAnnotatedMocksCapture = null;
    }

    /**
     * Wrapper for Mockito verifyNoMoreInteractions
     * @see org.mockito.Mockito#verifyNoMoreInteractions(Object...)
     */
    public static void verifyNoMoreInteractionsExtension() {
        validateMethodUse();
        Mockito.verifyNoMoreInteractions(allAnnotatedMocksCapture);
    }

    /**
     * Wrapper for Mockito verifyZeroInteractions
     * @see org.mockito.Mockito#verifyZeroInteractions(Object...)
     */
    public static void verifyZeroInteractionsExtension() {
        validateMethodUse();
        Mockito.verifyZeroInteractions(allAnnotatedMocksCapture);
    }

    /**
     * Wrapper for Mockito reset
     * @see org.mockito.Mockito#reset(Object[])
     */
    public static void resetExtension() {
        validateMethodUse();
        Mockito.reset(allAnnotatedMocksCapture);
    }

    /**
     * Wrapper for Mockito inOrder
     * @see org.mockito.Mockito#inOrder(Object...)
     */
    public static InOrder inOrderExtension() {
        validateMethodUse();
        return Mockito.inOrder(allAnnotatedMocksCapture);
    }

    /**
     * Wrapper for MockitoDebuggerImpl printInvocations
     * @see org.mockito.MockitoDebugger#printInvocations(Object...)
     */
    public static void debugPrintInvocationsExtension(){
        validateMethodUse();
        mockitoDebugger.printInvocations(allAnnotatedMocksCapture);
    }

    private static void validateMethodUse(){
        if (allAnnotatedMocksCapture == null){
            throw new IllegalArgumentException("MockitoAnnotationsExtension.initMocksExtension must be called prior to this method call");
        }
    }

    private static boolean isInjectedMock(Field field){
        return field.getAnnotation(org.mockito.Mock.class) != null;
    }

    private static Object[] getAllAnnotatedMocks(final Object testClass) {
        try{
            Set<Object> mocks = new HashSet<Object>();
            for (Field field : testClass.getClass().getDeclaredFields()) {
                if (isInjectedMock(field)){
                    field.setAccessible(true);
                    mocks.add(field.get(testClass));
                }
            }
            return mocks.toArray();
        }catch (Exception unexpectedWrapped){
            throw new RuntimeException(unexpectedWrapped);
        }
    }
}
